import "@site/src/languages/highlight";
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Developing Editors and Tools with ImGui

## 1. Introduction

In the game development process, an intuitive and efficient user interface (UI) is essential for editors and debugging tools. However, Dora SSR, as a code-focused engine, currently does not provide built-in editors or debugging tools. Therefore, developers need to quickly develop and customize these tools according to their game projects' needs. Fortunately, Dora SSR provides the **ImGui** library, which makes it easy to create such auxiliary UIs.

**ImGui** (Immediate Mode GUI) is an immediate mode graphical user interface library widely used for its simplicity and efficiency. This tutorial will introduce how to use the **ImGui** library provided by **Dora SSR** to develop UIs for game editors or debugging tools.

## 2. Philosophy, Advantages, and Disadvantages of ImGui Framework

### 2.1 Philosophy

The core philosophy of ImGui is **immediate mode**, which means that the UI is redrawn every frame. This differs from traditional retained mode, which maintains a UI state tree; immediate mode directly draws the UI based on the current program state.

### 2.2 Advantages

- **Easy to Use**: No need to manage complex UI states; UI elements can be described directly in code.
- **Rapid Iteration**: Suitable for quick prototyping and developing debugging tools.
- **Lightweight**: No need to integrate large UI frameworks, reducing resource consumption.
- **Highly Flexible**: Can be easily embedded into the existing game engine rendering loop.

### 2.3 Disadvantages

- **Not Suitable for Complex UIs**: It may not be ideal for applications requiring highly interactive and complex layouts.
- **Limited Styling**: The default visual style is relatively simple, and customization requires extra work, which may not meet the visual demands of game projects.
- **Performance Overhead**: In very complex UI scenarios, redrawing every frame may lead to performance issues.

## 4. Basic Usage

### 4.1 Creating a Simple Window

The following example demonstrates how to create a simple ImGui window:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")

threadLoop(function()
	ImGui.Begin("Example Window", function()
		ImGui.Text("Welcome to ImGui with Dora SSR!")
		ImGui.Separator()
		ImGui.TextWrapped("This is a simple example window showcasing basic text and a separator.")
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")

threadLoop(function(): boolean
	ImGui.Begin("Example Window", function()
		ImGui.Text("Welcome to ImGui with Dora SSR!")
		ImGui.Separator()
		ImGui.TextWrapped("This is a simple example window showcasing basic text and a separator.")
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import { threadLoop } from "Dora";
import * as ImGui from "ImGui";

threadLoop(() => {
	ImGui.Begin("Example Window", () => {
		ImGui.Text("Welcome to ImGui with Dora SSR!");
		ImGui.Separator();
		ImGui.TextWrapped("This is a simple example window showcasing basic text and a separator.");
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

threadLoop ->
	Begin "Example Window", ->
		Text "Welcome to ImGui with Dora SSR!"
		Separator!
		TextWrapped "This is a simple example window showcasing basic text and a separator."
```

</TabItem>
</Tabs>

**Explanation**:

- The `threadLoop` function is used to repeatedly execute operations in the main thread.
- The `ImGui.Begin` function is used to create a window and specify the window's title.
- The `ImGui.Text` function is used to draw text.
- The `ImGui.Separator` function is used to draw a separator line.
- The `ImGui.TextWrapped` function is used to draw a block of text with automatic line wrapping.

### 4.2 Adding Interactive Elements

You can add interactive elements such as buttons and input fields in the window:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")

local inputText = Buffer(200)
inputText.text = "Default Text"
threadLoop(function()
	ImGui.Begin("Interaction Example", function()
		if ImGui.Button("Click Me") then
			print("Button Clicked!")
		end
		if ImGui.InputText("Input Field", inputText) then
			print("Input Content: " .. inputText.text)
		end
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")

local inputText = Buffer(200)
inputText.text = "Default Text"
threadLoop(function(): boolean
	ImGui.Begin("Interaction Example", function()
		if ImGui.Button("Click Me") then
			print("Button Clicked!")
		end
		if ImGui.InputText("Input Field", inputText) then
			print("Input Content: " .. inputText.text)
		end
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import { threadLoop, Buffer } from "Dora";
import * as ImGui from "ImGui";

const inputText = Buffer(200);
inputText.text = "Default Text";
threadLoop(() => {
	ImGui.Begin("Interaction Example", () => {
		if (ImGui.Button("Click Me")) {
			print("Button Clicked!");
		}
		if (ImGui.InputText("Input Field", inputText)) {
			print(`Input Content: ${inputText.text}`);
		}
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

inputText = with Buffer 200
	.text = "Default Text"
threadLoop ->
	Begin "Interaction Example", ->
		if Button "Click Me"
			print "Button Clicked!"
		if InputText "Input Field", inputText
			print "Input Content：" .. inputText.text
```

</TabItem>
</Tabs>

**Explanation**:

- The `ImGui.Button` function is used to create a button and specify the button's label.
- The `ImGui.InputText` function is used to create an input field and specify the field's label and buffer.

## 5. Example of Creating a Game Editor

### 5.1 Object Property Editor

The object property editor is a core component of the game editor, used to view and modify the properties of game objects.

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")
local Vec2 <const> = require("Vec2")

-- Assume we have a game object with the following properties
local gameObject = {
	name = "Player",
	position = { x = 0.0, y = 0.0 },
	rotation = 0.0,
	scale = { x = 1.0, y = 1.0 },
	isActive = true
}

local nameBuffer = Buffer(100)
nameBuffer.text = gameObject.name

threadLoop(function()
	ImGui.SetNextWindowSize(Vec2(300, 400), "FirstUseEver")
	ImGui.Begin("Object Property Editor", function()
		-- Edit object name
		if ImGui.InputText("Name", nameBuffer) then
			gameObject.name = nameBuffer.text
		end

		-- Edit position
		local changed, x, y = ImGui.InputFloat2("Position", gameObject.position.x, gameObject.position.y)
		if changed then
			gameObject.position.x = x
			gameObject.position.y = y
		end

		-- Edit rotation
		local changed, rotation = ImGui.DragFloat("Rotation", gameObject.rotation, 1.0, 0.0, 360.0, "%.1f°")
		if changed then
			gameObject.rotation = rotation
		end

		-- Edit scale
		local changed, sx, sy = ImGui.InputFloat2("Scale", gameObject.scale.x, gameObject.scale.y)
		if changed then
			gameObject.scale.x = sx
			gameObject.scale.y = sy
		end

		-- Edit active state
		local changed, isActive = ImGui.Checkbox("Is Active", gameObject.isActive)
		if changed then
			gameObject.isActive = isActive
		end

		-- Output the current state of the object
		if ImGui.Button("Output State") then
			print("Current Object State:")
			p(gameObject)
		end
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")
local Vec2 <const> = require("Vec2")

-- Assume we have a game object with the following properties
local gameObject = {
	name = "Player",
	position = { x = 0.0, y = 0.0 },
	rotation = 0.0,
	scale = { x = 1.0, y = 1.0 },
	isActive = true
}

local nameBuffer = Buffer(100)
nameBuffer.text = gameObject.name

threadLoop(function(): boolean
	ImGui.SetNextWindowSize(Vec2(300, 400), "FirstUseEver")
	ImGui.Begin("Object Property Editor", function()
		-- Edit object name
		if ImGui.InputText("Name", nameBuffer) then
			gameObject.name = nameBuffer.text
		end

		-- Edit position
		local changed, x, y = ImGui.InputFloat2("Position", gameObject.position.x, gameObject.position.y)
		if changed then
			gameObject.position.x = x
			gameObject.position.y = y
		end

		-- Edit rotation
		local changed, rotation = ImGui.DragFloat("Rotation", gameObject.rotation, 1.0, 0.0, 360.0, "%.1f°")
		if changed then
			gameObject.rotation = rotation
		end

		-- Edit scale
		local changed, sx, sy = ImGui.InputFloat2("Scale", gameObject.scale.x, gameObject.scale.y)
		if changed then
			gameObject.scale.x = sx
			gameObject.scale.y = sy
		end

		-- Edit active state
		local changed, isActive = ImGui.Checkbox("Is Active", gameObject.isActive)
		if changed then
			gameObject.isActive = isActive
		end

		-- Output the current state of the object
		if ImGui.Button("Output State") then
			print("Current Object State:")
			p(gameObject)
		end
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import { threadLoop, Buffer, Vec2 } from "Dora";
import * as ImGui from "ImGui";
import { SetCond } from "ImGui";

const gameObject = {
	name: "Player",
	position: { x: 0.0, y: 0.0 },
	rotation: 0.0,
	scale: { x: 1.0, y: 1.0 },
	isActive: true
}

const nameBuffer = Buffer(100);
nameBuffer.text = gameObject.name;

threadLoop(() => {
	ImGui.SetNextWindowSize(Vec2(300, 400), SetCond.FirstUseEver);
	ImGui.Begin("Object Property Editor", () => {
		// Edit object name
		if (ImGui.InputText("Name", nameBuffer)) {
			gameObject.name = nameBuffer.text;
		}

		// Edit position
		{
			const [changed, x, y] = ImGui.InputFloat2("Position", gameObject.position.x, gameObject.position.y);
			if (changed) {
				gameObject.position.x = x;
				gameObject.position.y = y;
			}
		}

		// Edit rotation
		{
			const [changed, rotation] = ImGui.DragFloat("Rotation", gameObject.rotation, 1.0, 0.0, 360.0, "%.1f°");
			if (changed) {
				gameObject.rotation = rotation;
			}
		}

		// Edit scale
		{
			const [changed, sx, sy] = ImGui.InputFloat2("Scale", gameObject.scale.x, gameObject.scale.y);
			if (changed) {
				gameObject.scale.x = sx;
				gameObject.scale.y = sy;
			}
		}

		// Edit active state
		{
			const [changed, isActive] = ImGui.Checkbox("Is Active", gameObject.isActive);
			if (changed) {
				gameObject.isActive = isActive;
			}
		}

		// Output the current state of the object
		if (ImGui.Button("Output State")) {
			print("Current Object State:");
			p(gameObject);
		}
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

gameObject =
	name: "Player"
	position:
		x: 0.0
		y: 0.0
	rotation: 0.0,
	scale:
		x: 1.0
		y: 1.0
	isActive: true

nameBuffer = with Buffer 100
	.text = gameObject.name

threadLoop ->
	SetNextWindowSize Vec2(300, 400), "FirstUseEver"
	Begin "Object Property Editor", ->
		-- Edit object name
		if InputText "Name", nameBuffer
			gameObject.name = nameBuffer.text

		-- Edit position
		if changed, x, y := InputFloat2 "Position", gameObject.position.x, gameObject.position.y
			gameObject.position.x = x
			gameObject.position.y = y

		-- Edit rotation
		if changed, rotation := DragFloat "Rotation", gameObject.rotation, 1.0, 0.0, 360.0, "%.1f°"
			gameObject.rotation = rotation

		-- Edit scale
		if changed, sx, sy := InputFloat2 "Scale", gameObject.scale.x, gameObject.scale.y
			gameObject.scale.x = sx
			gameObject.scale.y = sy

		-- Edit active state
		if changed, isActive := Checkbox "Is Active", gameObject.isActive
			gameObject.isActive = isActive

		-- Output the current state of the object
		if Button "Output State"
			print "Current Object State:"
			p gameObject
```

</TabItem>
</Tabs>

**Explanation**:

- Use `InputText` to edit string properties.
- Use `InputFloat2` and `DragFloat` to edit numerical properties.
- Use `Checkbox` to edit boolean properties.

### 5.2 Scene Hierarchy View

The Scene Hierarchy View displays all game objects in the scene, presented in a tree structure.

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui = require("ImGui")
local threadLoop = require("threadLoop")

-- Assume we have a list of scene objects with parent-child relationships
local sceneObjects = {
	{
		name = "Root",
		children = {
			{
				name = "Player",
				children = {}
			},
			{
				name = "Enemy",
				children = {
					{ name = "Enemy1", children = {} },
					{ name = "Enemy2", children = {} },
				}
			},
		}
	}
}

local leafFlags = {"Leaf"}
local empty = function() end

-- Recursive function to draw the scene tree
local function drawSceneTree(nodes)
	for _, node in ipairs(nodes) do
		if #node.children > 0 then
			ImGui.TreeNode(node.name, function()
				drawSceneTree(node.children)
			end)
		else
			ImGui.TreeNodeEx(node.name, node.name, leafFlags, empty)
		end
	end
end

threadLoop(function()
	ImGui.Begin("Scene Hierarchy View", function()
		drawSceneTree(sceneObjects)
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui = require("ImGui")
local threadLoop = require("threadLoop")

local record Node
	name: string
	children: {Node}
end

-- Assume we have a list of scene objects with parent-child relationships
local sceneObjects: {Node} = {
	{
		name = "Root",
		children = {
			{
				name = "Player",
				children = {}
			},
			{
				name = "Enemy",
				children = {
					{ name = "Enemy1", children = {} },
					{ name = "Enemy2", children = {} },
				}
			},
		}
	}
}

local leafFlags = {"Leaf"}
local empty = function() end

-- Recursive function to draw the scene tree
local function drawSceneTree(nodes: {Node})
	for _, node in ipairs(nodes) do
		if #node.children > 0 then
			ImGui.TreeNode(node.name, function()
				drawSceneTree(node.children)
			end)
		else
			ImGui.TreeNodeEx(node.name, node.name, leafFlags, empty)
		end
	end
end

threadLoop(function(): boolean
	ImGui.Begin("Scene Hierarchy View", function()
		drawSceneTree(sceneObjects)
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import * as ImGui from "ImGui";
import { threadLoop } from "Dora";
import { TreeNodeFlag } from "ImGui";

interface Node {
	name: string;
	children: Node[];
}

// Assume we have a list of scene objects with parent-child relationships
const sceneObjects: Node[] = [
	{
		name: "Root",
		children: [
			{
				name: "Player",
				children: []
			},
			{
				name: "Enemy",
				children: [
					{ name: "Enemy1", children: [] },
					{ name: "Enemy2", children: [] }
				]
			}
		]
	}
];

const leafFlags = [TreeNodeFlag.Leaf];
const empty = () => {};

// Recursive function to draw the scene tree
function drawSceneTree(nodes: Node[]) {
	for (const node of nodes) {
		if (node.children.length > 0) {
			ImGui.TreeNode(node.name, () => {
				drawSceneTree(node.children);
			});
		} else {
			ImGui.TreeNodeEx(node.name, node.name, leafFlags, empty);
		}
	}
}

threadLoop(() => {
	ImGui.Begin("Scene Hierarchy View", () => {
		drawSceneTree(sceneObjects);
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

-- Assume we have a list of scene objects with parent-child relationships
sceneObjects =
	* name: "Root"
		children:
			* name: "Player"
				children: []
			* name: "Enemy"
				children:
					* name: "Enemy1"
						children: []
					* name: "Enemy2"
						children: []

leafFlags = {"Leaf"}
empty = ->

-- Recursive function to draw the scene tree
drawSceneTree = (sceneObjects) ->
	for node in *sceneObjects
		if #node.children > 0
			TreeNode node.name, ->
				drawSceneTree node.children
		else
			TreeNodeEx node.name, node.name, leafFlags, empty

threadLoop ->
	Begin "Scene Hierarchy View", ->
		drawSceneTree sceneObjects
```

</TabItem>
</Tabs>

**Note**:

- Use `TreeNode` and `TreePop` to create a tree structure.
- Recursively draw each node and its children.

### 5.3 Resource Browser

The Resource Browser is used to view and select resources in the project, such as textures, models, and audio files.

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")

-- Resource list
local resources = {
	textures = { "texture1.png", "texture2.png", "texture3.png" },
	models = { "model1.obj", "model2.obj" },
	sounds = { "sound1.wav", "sound2.wav" }
}

threadLoop(function()
	ImGui.Begin("Resource Browser", function()
		if ImGui.CollapsingHeader("Textures") then
			for _, texture in ipairs(resources.textures) do
				if ImGui.Selectable(texture) then
					print("Selected Texture: " .. texture)
				end
			end
		end

		if ImGui.CollapsingHeader("Models") then
			for _, model in ipairs(resources.models) do
				if ImGui.Selectable(model) then
					print("Selected Model: " .. model)
				end
			end
		end

		if ImGui.CollapsingHeader("Audio") then
			for _, sound in ipairs(resources.sounds) do
				if ImGui.Selectable(sound) then
					print("Selected Audio: " .. sound)
				end
			end
		end
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")

-- Resource list
local resources = {
	textures = { "texture1.png", "texture2.png", "texture3.png" },
	models = { "model1.obj", "model2.obj" },
	sounds = { "sound1.wav", "sound2.wav" }
}

threadLoop(function(): boolean
	ImGui.Begin("Resource Browser", function()
		if ImGui.CollapsingHeader("Textures") then
			for _, texture in ipairs(resources.textures) do
				if ImGui.Selectable(texture) then
					print("Selected Texture: " .. texture)
				end
			end
		end

		if ImGui.CollapsingHeader("Models") then
			for _, model in ipairs(resources.models) do
				if ImGui.Selectable(model) then
					print("Selected Model: " .. model)
				end
			end
		end

		if ImGui.CollapsingHeader("Audio") then
			for _, sound in ipairs(resources.sounds) do
				if ImGui.Selectable(sound) then
					print("Selected Audio: " .. sound)
				end
			end
		end
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import * as ImGui from "ImGui";
import { threadLoop } from "Dora";

// Resource list
const resources = {
	textures: ["texture1.png", "texture2.png", "texture3.png"],
	models: ["model1.obj", "model2.obj"],
	sounds: ["sound1.wav", "sound2.wav"]
};

threadLoop(() => {
	ImGui.Begin("Resource Browser", () => {
		if (ImGui.CollapsingHeader("Textures")) {
			for (const texture of resources.textures) {
				if (ImGui.Selectable(texture)) {
					print(`Selected Texture: ${texture}`);
				}
			}
		}

		if (ImGui.CollapsingHeader("Models")) {
			for (const model of resources.models) {
				if (ImGui.Selectable(model)) {
					print(`Selected Model: ${model}`);
				}
			}
		}

		if (ImGui.CollapsingHeader("Audio")) {
			for (const sound of resources.sounds) {
				if (ImGui.Selectable(sound)) {
					print(`Selected Audio: ${sound}`);
				}
			}
		}
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

-- Resource list
resources =
	textures: ["texture1.png", "texture2.png", "texture3.png"]
	models: ["model1.obj", "model2.obj"]
	sounds: ["sound1.wav", "sound2.wav"]

threadLoop ->
	Begin "Resource Browser", ->
		if CollapsingHeader "Textures"
			for _, texture in ipairs resources.textures
				if Selectable texture
					print "Selected Texture: " .. texture

		if CollapsingHeader "Models"
			for _, model in ipairs resources.models
				if Selectable model
					print "Selected Model: " .. model

		if CollapsingHeader "Audio"
			for _, sound in ipairs resources.sounds
				if Selectable sound
					print "Selected Audio: " .. sound
```

</TabItem>
</Tabs>

**Note**:

- Use `CollapsingHeader` to group resource types.
- Use `Selectable` for list items to allow users to select resources.

### 5.4 Material Editor

The Material Editor allows users to adjust material properties such as color, texture, and shader parameters.

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")
local Color3 <const> = require("Color3")

-- Material object
local material = {
	name = "BasicMaterial",
	color = { r = 255, g = 255, b = 255 },
	texture = "default.png",
	shininess = 32.0
}

-- Available texture list
local textures = { "default.png", "texture1.png", "texture2.png" }
local currentTextureIndex = 1

local nameBuffer = Buffer(100)
nameBuffer.text = material.name

threadLoop(function()
	ImGui.Begin("Material Editor", function()
		-- Edit material name
		if ImGui.InputText("Name", nameBuffer) then
			material.name = nameBuffer.text
		end

		-- Edit color
		local color = Color3(material.color.r, material.color.g, material.color.b)
		if ImGui.ColorEdit3("Color", color) then
			material.color.r, material.color.g, material.color.b = color.r, color.g, color.b
		end

		local changed = false
		changed, currentTextureIndex = ImGui.Combo("Texture", currentTextureIndex, textures)
		if changed then
			material.texture = textures[currentTextureIndex]
		end

		-- Edit shininess
		local changed, shininess = ImGui.DragFloat("Shininess", material.shininess, 1.0, 0.0, 128.0, "%.0f")
		if changed then
			material.shininess = shininess
		end

		-- Output current material status
		if ImGui.Button("Output Status") then
			print("Current Material Status:")
			p(material)
		end
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")
local Color3 <const> = require("Color3")

-- Material object
local material = {
	name = "BasicMaterial",
	color = { r = 255, g = 255, b = 255 },
	texture = "default.png",
	shininess = 32.0
}

-- Available texture list
local textures = { "default.png", "texture1.png", "texture2.png" }
local currentTextureIndex = 1

local nameBuffer = Buffer(100)
nameBuffer.text = material.name

threadLoop(function(): boolean
	ImGui.Begin("Material Editor", function()
		-- Edit material name
		if ImGui.InputText("Name", nameBuffer) then
			material.name = nameBuffer.text
		end

		-- Edit color
		local color = Color3(material.color.r, material.color.g, material.color.b)
		if ImGui.ColorEdit3("Color", color) then
			material.color.r, material.color.g, material.color.b = color.r, color.g, color.b
		end

		local changed = false
		changed, currentTextureIndex = ImGui.Combo("Texture", currentTextureIndex, textures)
		if changed then
			material.texture = textures[currentTextureIndex]
		end

		-- Edit shininess
		local changed, shininess = ImGui.DragFloat("Shininess", material.shininess, 1.0, 0.0, 128.0, "%.0f")
		if changed then
			material.shininess = shininess
		end

		-- Output current material status
		if ImGui.Button("Output Status") then
			print("Current Material Status:")
			p(material)
		end
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import * as ImGui from "ImGui";
import { threadLoop, Buffer, Color3 } from "Dora";

// Material object
const material = {
	name: "BasicMaterial",
	color: { r: 255, g: 255, b: 255 },
	texture: "default.png",
	shininess: 32.0
};

// Available texture list
const textures = ["default.png", "texture1.png", "texture2.png"];
let currentTextureIndex = 1;

const nameBuffer = Buffer(100);
nameBuffer.text = material.name;

threadLoop(() => {
	ImGui.Begin("Material Editor", () => {
		// Edit material name
		if (ImGui.InputText("Name", nameBuffer)) {
			material.name = nameBuffer.text;
		}

		// Edit color
		const color = Color3(material.color.r, material.color.g, material.color.b);
		if (ImGui.ColorEdit3("Color", color)) {
			material.color.r = color.r;
			material.color.g = color.g;
			material.color.b = color.b;
		}

		// Select texture
		{
			let changed = false;
			[changed, currentTextureIndex] = ImGui.Combo("Texture", currentTextureIndex, textures)
			if (changed) {
				material.texture = textures[currentTextureIndex];
			}
		}

		// Edit shininess
		{
			const [changed, shininess] = ImGui.DragFloat("Shininess", material.shininess, 1.0, 0.0, 128.0, "%.0f")
			if (changed) {
				material.shininess = shininess;
			}
		}

		// Output current material status
		if (ImGui.Button("Output Status")) {
			print("Current Material Status:");
			p(material);
		}
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

-- Material object
material =
	name: "BasicMaterial"
	color: { r: 255, g: 255, b: 255 }
	texture: "default.png"
	shininess: 32.0

textures = ["default.png", "texture1.png", "texture2.png"]
currentTextureIndex = 1

nameBuffer = with Buffer 100
	.text = material.name

threadLoop ->
	Begin "Material Editor", ->
		if InputText "Name", nameBuffer
			material.name = nameBuffer.text

		-- Edit color
		color = Color3 material.color.r, material.color.g, material.color.b
		if ColorEdit3 "Color", color
			material.color.r, material.color.g, material.color.b = color.r, color.g, color.b

		-- Select texture
		changed, currentTextureIndex = Combo "Texture", currentTextureIndex, textures
		if changed
			material.texture = textures[currentTextureIndex]

		-- Edit shininess
		changed, shininess = DragFloat "Shininess", material.shininess, 1.0, 0.0, 128.0, "%.0f"
		if changed
			material.shininess = shininess

		-- Output current material status
		if Button "Output Status"
			print "Current Material Status:"
			p material
```

</TabItem>
</Tabs>

**Note**:

- Use `ColorEdit3` for color selection.
- Use `Combo` to create a dropdown menu for selecting textures.
- Use `DragFloat` to adjust numerical parameters.

### 5.5 Console Window

Implement a simple console window for inputting commands and displaying logs.

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")
local Vec2 <const> = require("Vec2")

local logs = {}
local inputBuffer = Buffer(200)

threadLoop(function()
	ImGui.SetNextWindowSize(Vec2(300, 200), "FirstUseEver")
	ImGui.Begin("Console", function()
		-- Display log area
		ImGui.BeginChild("LogArea", Vec2(0, -25), function()
			for _, log in ipairs(logs) do
				ImGui.TextWrapped(log)
			end
			if ImGui.GetScrollY() >= ImGui.GetScrollMaxY() then
				ImGui.SetScrollHereY(1.0)
			end
		end)
		-- Input area
		if ImGui.InputText("Enter Command", inputBuffer, { "EnterReturnsTrue" }) then
			local command = inputBuffer.text
			table.insert(logs, "> " .. command)
			-- Execute command (here simply echoing)
			table.insert(logs, "Execution Result: Command [" .. command .. "] has been executed.")
			inputBuffer.text = ""
		end
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Buffer <const> = require("Buffer")
local Vec2 <const> = require("Vec2")

local logs: {string} = {}
local inputBuffer = Buffer(200)

threadLoop(function(): boolean
	ImGui.SetNextWindowSize(Vec2(300, 200), "FirstUseEver")
	ImGui.Begin("Console", function()
		-- Display log area
		ImGui.BeginChild("LogArea", Vec2(0, -25), function()
			for _, log in ipairs(logs) do
				ImGui.TextWrapped(log)
			end
			if ImGui.GetScrollY() >= ImGui.GetScrollMaxY() then
				ImGui.SetScrollHereY(1.0)
			end
		end)
		-- Input area
		if ImGui.InputText("Enter Command", inputBuffer, { "EnterReturnsTrue" }) then
			local command = inputBuffer.text
			table.insert(logs, "> " .. command)
			-- Execute command (here simply echoing)
			table.insert(logs, "Execution Result: Command [" .. command .. "] has been executed.")
			inputBuffer.text = ""
		end
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import * as ImGui from "ImGui";
import { threadLoop, Buffer, Vec2 } from "Dora";
import { InputTextFlag, SetCond } from "ImGui";

const logs: string[] = [];
const inputBuffer = Buffer(200);

threadLoop(() => {
	ImGui.SetNextWindowSize(Vec2(300, 200), SetCond.FirstUseEver);
	ImGui.Begin("Console", () => {
		// Display log area
		ImGui.BeginChild("LogArea", Vec2(0, -25), () => {
			for (const log of logs) {
				ImGui.TextWrapped(log);
			}
			if (ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) {
				ImGui.SetScrollHereY(1.0);
			}
		});
		// Input area
		if (ImGui.InputText("Enter Command", inputBuffer, [InputTextFlag.EnterReturnsTrue])) {
			const command = inputBuffer.text;
			logs.push(`> ${command}`);
			logs.push(`Execution Result: Command [${command}] has been executed.`);
			inputBuffer.text = "";
		}
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

-- Log list
logs = {}

inputBuffer = Buffer 200

threadLoop ->
	Begin "Console", ->
		-- Display log area
		BeginChild "LogArea", Vec2(0, -25), ->
			for log in *logs
				TextWrapped log
			if GetScrollY! >= GetScrollMaxY!
				SetScrollHereY 1.0
		-- Input area
		if InputText "Enter Command", inputBuffer, ["EnterReturnsTrue",]
			command = inputBuffer.text
			table.insert logs, "> " .. command
			table.insert logs, "Execution Result: Command [" .. command .. "] has been executed."
			inputBuffer.text = ""
```

</TabItem>
</Tabs>

**Explanation**：

- Use `BeginChild` to create a log display area.
- Use `InputText` to accept user input and handle commands upon pressing Enter.
- Use `SetScrollHereY` to keep the scrollbar at the bottom.

### 5.6 Status Bar and Toolbar

Add a status bar and toolbar to the editor window, providing quick access to commonly used functions.

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Vec2 <const> = require("Vec2")

threadLoop(function()
	ImGui.Begin("Editor Main Window", { "MenuBar", "AlwaysAutoResize" }, function()
		-- Toolbar
		ImGui.BeginMenuBar(function()
			ImGui.BeginMenu("File", function()
				if ImGui.MenuItem("New") then
					print("New File")
				end
				if ImGui.MenuItem("Save") then
					print("Save File")
				end
			end)
			ImGui.BeginMenu("Edit", function()
				if ImGui.MenuItem("Undo") then
					print("Undo Operation")
				end
			end)
		end)

		-- Main content area
		ImGui.Text("This is the main content area")
		ImGui.Dummy(Vec2(0, 100))

		-- Status bar
		ImGui.BeginChild("StatusBar", Vec2(0, 20), function()
			ImGui.Text("Status: Ready")
		end)
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local ImGui <const> = require("ImGui")
local threadLoop <const> = require("threadLoop")
local Vec2 <const> = require("Vec2")

threadLoop(function(): boolean
	ImGui.Begin("Editor Main Window", { "MenuBar", "AlwaysAutoResize" }, function()
		-- Toolbar
		ImGui.BeginMenuBar(function()
			ImGui.BeginMenu("File", function()
				if ImGui.MenuItem("New") then
					print("New File")
				end
				if ImGui.MenuItem("Save") then
					print("Save File")
				end
			end)
			ImGui.BeginMenu("Edit", function()
				if ImGui.MenuItem("Undo") then
					print("Undo Operation")
				end
			end)
		end)

		-- Main content area
		ImGui.Text("This is the main content area")
		ImGui.Dummy(Vec2(0, 100))

		-- Status bar
		ImGui.BeginChild("StatusBar", Vec2(0, 20), function()
			ImGui.Text("Status: Ready")
		end)
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
import * as ImGui from "ImGui";
import { WindowFlag } from "ImGui";
import { threadLoop, Vec2 } from "Dora";

threadLoop(() => {
	ImGui.Begin("Editor Main Window", [ WindowFlag.MenuBar, WindowFlag.AlwaysAutoResize ], () => {
		// Toolbar
		ImGui.BeginMenuBar(() => {
			ImGui.BeginMenu("File", () => {
				if (ImGui.MenuItem("New")) {
					print("New File");
				}
				if (ImGui.MenuItem("Save")) {
					print("Save File");
				}
			});
			ImGui.BeginMenu("Edit", () => {
				if (ImGui.MenuItem("Undo")) {
					print("Undo Operation");
				}
			});
		});

		// Main content area
		ImGui.Text("This is the main content area")
		ImGui.Dummy(Vec2(0, 100))

		// Status bar
		ImGui.BeginChild("StatusBar", Vec2(0, 20), () => {
			ImGui.Text("Status: Ready")
		})
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
_ENV = Dora Dora.ImGui

threadLoop ->
	Begin "Editor Main Window", ["MenuBar", "AlwaysAutoResize"], ->
		-- Toolbar
		BeginMenuBar ->
			BeginMenu "File", ->
				if MenuItem "New"
					print "New File"
				if MenuItem "Save"
					print "

Save File"
			BeginMenu "Edit", ->
				if MenuItem "Undo"
					print "Undo Operation"

		-- Main content area
		Text "This is the main content area"
		Dummy Vec2 0, 100

		-- Status bar
		BeginChild "StatusBar", Vec2(0, 20), ->
			Text "Status: Ready"
```

</TabItem>
</Tabs>

**Explanation**：

- Use `BeginMenuBar` and `EndMenuBar` to create a menu bar or toolbar.
- Add a `BeginChild` in the main window to simulate the status bar.

## 6. Optimization Tips: Extracting Anonymous Functions to Reduce Memory Allocation

### 6.1 Problem Analysis

When developing with the **ImGui** library, a significant number of anonymous functions (closures) may be created each frame, leading to frequent memory allocation and garbage collection, which can negatively impact performance.

### 6.2 Solution

**Extract Anonymous Functions**: Extract anonymous functions into local functions to avoid creating new function objects every frame.

### 6.3 Optimization Methods

#### 6.3.1 Extracting Anonymous Functions as Local Functions

**Example**:

* Before Optimization:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
threadLoop(function()
	ImGui.Begin("Example Window", function()
		ImGui.Text("This is an example window")
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
threadLoop(function(): boolean
	ImGui.Begin("Example Window", function()
		ImGui.Text("This is an example window")
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
threadLoop(() => {
	ImGui.Begin("Example Window", () => {
		ImGui.Text("This is an example window");
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
threadLoop ->
	Begin "Example Window", ->
		Text "This is an example window"
```

</TabItem>
</Tabs>

* After Optimization:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local function drawExampleWindow()
	ImGui.Text("This is an example window")
end

threadLoop(function()
	ImGui.Begin("Example Window", drawExampleWindow)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local function drawExampleWindow()
	ImGui.Text("This is an example window")
end

threadLoop(function()
	ImGui.Begin("Example Window", drawExampleWindow)
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
drawExampleWindow = () => {
	ImGui.Text("This is an example window");
};
threadLoop(() => {
	ImGui.Begin("Example Window", drawExampleWindow);
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
drawExampleWindow = ->
	Text "This is an example window"

threadLoop ->
	Begin "Example Window", drawExampleWindow
```

</TabItem>
</Tabs>

#### 6.3.2 Using Function Caching Mechanism

**Example**:

* Before Optimization:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local objects = {
	{ name = "Object1", id = 1 },
	{ name = "Object2", id = 2 },
	{ name = "Object3", id = 3 },
}

threadLoop(function()
	ImGui.Begin("Object List", function()
		for i, obj in ipairs(objects) do
			ImGui.TreeNode(obj.name, function()
				ImGui.Text("Object ID: " .. obj.id)
			end)
		end
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local objects = {
	{ name = "Object1", id = 1 },
	{ name = "Object2", id = 2 },
	{ name = "Object3", id = 3 },
}

threadLoop(function(): boolean
	ImGui.Begin("Object List", function()
		for i, obj in ipairs(objects) do
			ImGui.TreeNode(obj.name, function()
				ImGui.Text("Object ID: " .. obj.id)
			end)
		end
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const objects = [
	{ name: "Object1", id: 1 },
	{ name: "Object2", id: 2 },
	{ name: "Object3", id: 3 },
];

threadLoop(() => {
	ImGui.Begin("Object List", () => {
		for (const obj of objects) {
			ImGui.TreeNode(obj.name, () => {
				ImGui.Text(`Object ID: ${obj.id}`);
			});
		}
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
objects =
	* name: "Object1", id: 1
	* name: "Object2", id: 2
	* name: "Object3", id: 3

threadLoop ->
	Begin "Object List", ->
		for obj in *objects
			TreeNode obj.name, ->
				Text "Object ID: " .. obj.id
```

</TabItem>
</Tabs>

* After Optimization:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local objects = {
	{ name = "Object1", id = 1 },
	{ name = "Object2", id = 2 },
	{ name = "Object3", id = 3 },
}

local function getTreeNodeFunction(obj)
	if not obj.nodeFunction then
		obj.nodeFunction = function()
			ImGui.Text("Object ID: " .. obj.id)
		end
	end
	return obj.nodeFunction
end

local function drawObjectList()
	for _, obj in ipairs(objects) do
		ImGui.TreeNode(obj.name, getTreeNodeFunction(obj))
	end
end

threadLoop(function(): boolean
	ImGui.Begin("Object List", drawObjectList)
	return false
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local record Object
	name: string
	id: integer
	nodeFunction: function()
end

local objects: {Object} = {
	{ name = "Object1", id = 1 },
	{ name = "Object2", id = 2 },
	{ name = "Object3", id = 3 },
}

local function getTreeNodeFunction(obj: Object): function()
	if not obj.nodeFunction then
		obj.nodeFunction = function()
			ImGui.Text("Object ID: " .. obj.id)
		end
	end
	return obj.nodeFunction
end

local function drawObjectList()
	for _, obj in ipairs(objects) do
		ImGui.TreeNode(obj.name, getTreeNodeFunction(obj))
	end
end

threadLoop(function(): boolean
	ImGui.Begin("Object List", drawObjectList)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
interface Object {
	name: string;
	id: number;
	nodeFunction?: (this: void) => void;
}

const objects: Object[] = [
	{ name: "Object1", id: 1 },
	{ name: "Object2", id: 2 },
	{ name: "Object3", id: 3 },
];

const getTreeNodeFunction = (obj: Object): () => void => {
	if (!obj.nodeFunction) {
		obj.nodeFunction = () => {
			ImGui.Text(`Object ID: ${obj.id}`);
		};
	}
	return obj.nodeFunction;
};

const drawObjectList = () => {
	for (const obj of objects) {
		ImGui.TreeNode(obj.name, getTreeNodeFunction(obj));
	}
};

threadLoop(() => {
	ImGui.Begin("Object List", drawObjectList);
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
objects =
	* name: "Object1", id: 1
	* name: "Object2", id: 2
	* name: "Object3", id: 3

getTreeNodeFunction = (obj) ->
	if not obj.nodeFunction
		obj.nodeFunction = ->
			ImGui.Text "Object ID: " .. obj.id
	obj.nodeFunction

drawObjectList = ->
	for obj in *objects
		TreeNode obj.name, getTreeNodeFunction obj

threadLoop ->
	Begin "Object List", drawObjectList
```

</TabItem>
</Tabs>

#### 6.3.3 Extracting Reused Variables Outside the Closure

**Example**:

* Before Optimization:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
threadLoop(function()
	ImGui.Begin("Example Window", { "AlwaysAutoResize" }, function()
		ImGui.Text("This is an example window")
	end)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
threadLoop(function(): boolean
	ImGui.Begin("Example Window", { "AlwaysAutoResize" }, function()
		ImGui.Text("This is an example window")
	end)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
threadLoop(() => {
	ImGui.Begin("Example Window", [ WindowFlag.AlwaysAutoResize ], () => {
		ImGui.Text("This is an example window");
	});
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
threadLoop ->
	Begin "Example Window", [ "AlwaysAutoResize" ], ->
		Text "This is an example window"
```

</TabItem>
</Tabs>

* After Optimization:

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local windowFlags = { "AlwaysAutoResize" }
local drawFunction = function()
	ImGui.Text("This is an example window")
end
threadLoop(function()
	ImGui.Begin("Example Window", windowFlags, drawFunction)
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local windowFlags = { "AlwaysAutoResize" }
local drawFunction = function()
	ImGui.Text("This is an example window")
end
threadLoop(function(): boolean
	ImGui.Begin("Example Window", windowFlags, drawFunction)
	return false
end)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const windowFlags = [ WindowFlag.AlwaysAutoResize ];
const drawFunction = () => {
	ImGui.Text("This is an example window");
};
threadLoop(() => {
	ImGui.Begin("Example Window", windowFlags, drawFunction);
	return false;
});
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
windowFlags = [ "AlwaysAutoResize" ]
drawFunction = ->
	Text "This is an example window"

threadLoop ->
	Begin "Example Window", windowFlags, drawFunction
```

</TabItem>
</Tabs>

### 6.4 Summary

By extracting anonymous functions to the outer layer of closures, you can:

- **Reduce Memory Allocation Each Frame**: Avoid frequent creation of new functions and objects, reducing garbage collection pressure.
- **Improve Performance**: Minimize unnecessary overhead, allowing your game editor to run more smoothly.
- **Enhance Code Structure**: Clearly separate logic, improving code readability and maintainability.

## 7. Development Recommendations

- **Fully Utilize Immediate Mode**: Since ImGui is immediate mode, you can dynamically update the UI based on real-time program states.
- **Pay Attention to Performance**: In complex UIs, minimize unnecessary drawing and use conditional statements to control UI element updates when necessary.
- **Organize Code Structure**: Encapsulate reusable UI components into functions to enhance code readability and maintainability.
- **Monitor Performance**: Use profiling tools to monitor memory allocation and CPU usage to identify performance bottlenecks in a timely manner.
- **Code Review**: Regularly review code to identify potential optimization points and avoid unnecessary resource wastage.
- **Learn Best Practices**: For more ImGui usage methods, refer to official documentation and community experiences to learn and apply the best coding practices.

## 8. Conclusion

Through this tutorial, you should have a comprehensive understanding of how to use the ImGui library in Dora SSR to develop UIs for game editors or debugging tools. With its simplicity and efficiency, ImGui is particularly suitable for tool development and rapid prototyping. I hope you can fully leverage its advantages in your actual projects.